package core;


import java.awt.image.*;
import java.awt.Color;
import java.util.*;

/**
 * @author daniele
 *
 */
public class TextureSynthetizer1 {
    
        private int _wIntorno = 5; //Intorni 5x5
    
        private BufferedImage _input_image;
        private BufferedImage _output_image;
        private List _points_xstar;
        private int _num_step;
        private Vector <Neighborhood> _intorniImmagineInput; //Per ogni p in X* esiste un intorno vicino in Zp
        private Vector <Neighborhood> _insiemeXp; //Equivale l'insieme
        private ClusteringTree _tree;
        
        public TextureSynthetizer1(int step, int width, int height, BufferedImage inIm)
        {
                _num_step = step;
                _input_image = inIm;
                
                _output_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                
                /*Inizializzo l'immagine con colori casuali*/
                int r = 0,
                    g = 0,
                    b = 0;
                Color auxColor; //Colore casuale creato ad ogni iterazione
                for(int i=0; i<width; i++)
                    for(int j=0; j<height; j++)
                    {
                        r = (int)(Math.random()*256);
                        g = (int)(Math.random()*256);
                        b = (int)(Math.random()*256);
                        auxColor = new Color(r, g, b);
                        _output_image.setRGB(i, j, auxColor.getRGB());
                    }
                
                _intorniImmagineInput = new Vector<Neighborhood>();
                //Popolo l'insieme di intorni dell'immagine di input
                //Questo insieme non deve essere più toccato
                for(int i=_wIntorno/2; i<_input_image.getWidth()-_wIntorno/2; i+=_wIntorno/2)
                    for(int j=_wIntorno/2; j<_input_image.getHeight()-_wIntorno/2; j+=_wIntorno/2)
                    {
                        _intorniImmagineInput.addElement(new Neighborhood(_wIntorno, i, j));
                    }
                System.gc();
                //Inizializzo e popolo l'insieme di intorni dell'immagine di output
                _insiemeXp = new Vector <Neighborhood>();
                for(int i=_wIntorno/2; i<_output_image.getWidth()-_wIntorno/2; i = i+_wIntorno/2)
                    for(int j=_wIntorno/2; j<_output_image.getHeight()-_wIntorno/2; j=j+_wIntorno/2)
                {
                    _insiemeXp.addElement(new Neighborhood(_wIntorno, i, j));
                }
                System.gc();
                //Creo l'albero contenente gli intorni di input e usato al M-Step
                _tree = new ClusteringTree();
                _tree.setImg(_input_image);
                _tree.buildTree(4, _intorniImmagineInput);
                
        }
        
        private double calculatePotEnergy(Neighborhood xp, Neighborhood zp)
        {
                double value = 0.0;
                /*int[][] redvalues1 = zp.getRedValuesFromImage(_input_image);
                int[][] redvalues2 = xp.getRedValuesFromImage(_output_image);
                int[][] bluevalues1 = zp.getBlueValuesFromImage(_input_image);
                int[][] bluevalues2 = xp.getBlueValuesFromImage(_output_image);
                int[][] greenvalues1 = zp.getGreenValuesFromImage(_input_image);
                int[][] greenvalues2 = xp.getGreenValuesFromImage(_output_image);*/
                int[][] valuesInput = xp.getLuminanceFromImage(_input_image);
                int[][] valuesOutput = xp.getLuminanceFromImage(_output_image);
                
                for (int i=0; i<xp.getWidth(); i++)
                    for(int j=0; j<xp.getWidth(); j++)
                    {
                        //Al momento lo calcolo soltanto su un canale
                        value = value + (Math.pow((float)valuesInput[i][j]/256 - (float)valuesOutput[i][j]/256, 2));
                        //value = value + (Math.pow((float)greenvalues1[i][j]/256 - (float)greenvalues2[i][j]/256, 2));
                        //value = value + (Math.pow((float)redvalues1[i][j]/256 - (float)bluevalues2[i][j]/256, 2));
                    }
                //return Math.sqrt(value);
                return value;
        }
        
        private double calculateTotalEnergy(Vector x, Vector z)
        {
            double sum = 0.0;
            for(int i=0; i<x.size(); i++)
            {
                sum = sum + calculatePotEnergy((Neighborhood)x.elementAt(i), (Neighborhood)z.elementAt(i));
            }
            return sum;
        }
        
        private void eStep(Vector zp, Vector xp)
        {       
            //Passo di minimizzazione
            //Per ogni intorno procedo alla sostituzione sull'immagine
            Neighborhood intornoZp = null;
            Neighborhood intornoXp = null;
            /*int[][] redvaluesZp, redvaluesXp;
            int[][] greenvaluesZp, greenvaluesXp;
            int[][] bluevaluesZp, bluevaluesXp;*/
            int[][] valuesZp;
            for(int i=0; i<xp.size(); i++)
            {
                intornoZp = (Neighborhood)zp.elementAt(i);
                intornoXp = (Neighborhood)xp.elementAt(i);
                
                valuesZp = intornoZp.getLuminanceFromImage(_input_image);
                //redvaluesXp = intornoXp.getRedValuesFromImage(_output_image);
                //greenvaluesZp = intornoZp.getGreenValuesFromImage(_input_image);
                //greenvaluesXp = intornoXp.getGreenValuesFromImage(_output_image);
                //bluevaluesZp = intornoZp.getBlueValuesFromImage(_input_image);
                //bluevaluesXp = intornoXp.getBlueValuesFromImage(_output_image);
                
                //Occorre effettuare il blending con i 3 intorni successivi
                //Ma prima scrivo l'intorno totalmente
                //e poi blendo con gli intorni successivi(3 in tutto)
                for(int x = 0; x<_wIntorno; x++)
                {
                    for(int y=0; y<_wIntorno; y++)
                    {
                        _output_image.setRGB((intornoXp.getCenterX()-_wIntorno/2)+x, (intornoXp.getCenterY()-_wIntorno/2)+y, new Color(valuesZp[x][y], valuesZp[x][y], valuesZp[x][y]).getRGB());
                    }
                }
                //Basso
                int nextCenterX = intornoXp.getCenterX()+_wIntorno/2+1;
                int nextCenterY = intornoXp.getCenterY();
                if(nextCenterX+_wIntorno/2<_output_image.getHeight())
                {
                    int next = getNeighborhoodByCenter(_insiemeXp, nextCenterX, nextCenterY);
                    if(next>=0)
                        blendNeighborhood(intornoXp, (Neighborhood)xp.elementAt(next), (Neighborhood)zp.elementAt(next));
                }
                //Destra
                nextCenterX = intornoXp.getCenterX();
                nextCenterY = intornoXp.getCenterY()+_wIntorno/2+1;
                if(nextCenterY+_wIntorno/2<_output_image.getWidth())
                {
                    int next = getNeighborhoodByCenter(_insiemeXp, nextCenterX, nextCenterY);
                    if(next>=0)
                        blendNeighborhood(intornoXp, (Neighborhood)xp.elementAt(next), (Neighborhood)zp.elementAt(next));
                }
                //Diagonale
                nextCenterX = intornoXp.getCenterX()+_wIntorno/2+1;
                nextCenterY = intornoXp.getCenterY()+_wIntorno/2+1;
                if(nextCenterY+_wIntorno/2<_output_image.getWidth() && nextCenterX+_wIntorno/2<_output_image.getHeight())
                {
                    int next = getNeighborhoodByCenter(_insiemeXp, nextCenterX, nextCenterY);
                    if(next>=0)
                        blendNeighborhood(intornoXp, (Neighborhood)xp.elementAt(next), (Neighborhood)zp.elementAt(next));
                }
            }
        }
        
        private Vector <Neighborhood> mStep(Vector xp)
        {
            Vector <Neighborhood> zp = new Vector<Neighborhood>();
            Iterator iter = xp.iterator();
            while(iter.hasNext())
            {
            	Neighborhood current = (Neighborhood)iter.next();
            	zp.add(_tree.findClosest(current, _output_image));
            }
            return zp;
        }
        
        private Vector <Neighborhood> dummyMStep(Vector xp)
        {
            int minEnergyIndex = 0;
            double minEnergy = 0.0;
            double energy = 0.0;
            Vector <Neighborhood> zp = new Vector <Neighborhood>();
            Neighborhood currentXp = null;
            Neighborhood currentIntornoImgInput = null;
            //Trovo l'intorno più vicino a ciascun elemento in xp in _input_image
            for(int s=0; s<xp.size(); s++)
            {
                currentXp = (Neighborhood)xp.elementAt(s);
                minEnergyIndex = 0;
                minEnergy = calculatePotEnergy(currentXp, (Neighborhood)_intorniImmagineInput.elementAt(0));
                for(int i=1; i<_intorniImmagineInput.size(); i++)
                {
                    energy = calculatePotEnergy(currentXp, (Neighborhood)_intorniImmagineInput.elementAt(i));
                    if(energy<minEnergy)
                    {
                        minEnergy = energy;
                        minEnergyIndex = i;
                    }
                }
                
                zp.add((Neighborhood)_intorniImmagineInput.elementAt(minEnergyIndex));
            }
            return zp;
        }
        
        // L'energia di un intorno sintetizzato è definita
        // come la distanza dal più vicino intorno dell'input
        // l'energia totale è la somma di tutti gli intorni
        public BufferedImage synthetize()
        {
            //La prima associazione avviene in maniera casuale
            Vector <Neighborhood> zp = new Vector<Neighborhood>();
            for(int i=0; i<_insiemeXp.size(); i++)
            {
                zp.addElement(_intorniImmagineInput.elementAt( (int)(Math.random()*_intorniImmagineInput.size()) ));
            }
            System.out.println("Energia totale iniziale: "+calculateTotalEnergy(_insiemeXp, zp));
            for (int s=0; s < _num_step; s++)
            {
                eStep(zp, _insiemeXp);
                System.out.println("Energia totale dopo passo E: "+calculateTotalEnergy(_insiemeXp, zp));
                //zp = dummyMStep(_insiemeXp);
                zp = mStep(_insiemeXp);
                System.out.println("Energia totale dopo passo M: "+calculateTotalEnergy(_insiemeXp, zp));
            }

            System.gc();
            return null; //Deve ritornare l'immagine finale alla fine del processo di sintesi
        }
        
        /**
         * Ritorna l'indice dell'intorno. Utile per andare a pescare il corrispettivo in Zp
         */
        private int getNeighborhoodByCenter(Vector v, int x, int y)
        {
            Neighborhood intorno = null;
            
            for(int i=0; i<v.size(); i++)
            {
               intorno = (Neighborhood)v.elementAt(i);
               if(intorno.getCenterX() == x && intorno.getCenterY() == y)
                   return i;
            }
            
            return -1;
        }
        /**
         * 
         * @param zp Intorno zp
         * @param xp Intorno xp
         * @param index1 indice del primo intorno con cui fare il blending
         * @param index2 indice del secondo intorno
         */
        private void blendNeighborhood(Neighborhood current, Neighborhood next, Neighborhood zpNearNext)
        {
            int[][] redvalues1, greenvalues1, bluevalues1;
            int[][] redvalues2, greenvalues2, bluevalues2;
            int[][] values1, values2;
            
            
            values1 = current.getLuminanceFromImage(_output_image);
            values2 = zpNearNext.getLuminanceFromImage(_input_image);
            /*greenvalues1 = current.getGreenValuesFromImage(_output_image);
            greenvalues2 = zpNearNext.getGreenValuesFromImage(_input_image);
            bluevalues1 = current.getBlueValuesFromImage(_output_image);
            bluevalues2 = zpNearNext.getBlueValuesFromImage(_input_image);
            */
            int distX = Math.abs(current.getCenterX() - next.getCenterX());
            int distY = Math.abs(current.getCenterY() - next.getCenterY());
            
            int red = 0;
            int green = 0;
            int blue = 0;
            
            int value = 0;
            
            int centerX = current.getCenterX();
            int centerY = current.getCenterY();
            
            if(distX == 0) //Siamo nel caso in basso
            {
               for(int i=0; i<_wIntorno/2; i++)//Righe
                   for(int j=0; j<_wIntorno; j++)//Colonne
                   {
                       /*red = (redvalues1[_wIntorno/2+1+i][j] + redvalues2[i][j])/2;
                       green = (greenvalues1[_wIntorno/2+1+i][j] + greenvalues2[i][j])/2;
                       blue = (bluevalues1[_wIntorno/2+1+i][j] + bluevalues2[i][j])/2;*/
                       
                       value = (values1[_wIntorno/2+1+i][j] + values2[i][j])/2;
                       
                       _output_image.setRGB(centerX+i, centerY-_wIntorno/2+j, new Color(value, value, value).getRGB());
                   }
            }
            else if(distY == 0)//Siamo nel caso a destra
            {
                for(int i=0; i<_wIntorno; i++)//Righe
                   for(int j=0; j<_wIntorno/2; j++)//Colonne
                   {
                       /*red = (redvalues1[i][_wIntorno/2+1+j] + redvalues2[i][j])/2;
                       green = (greenvalues1[i][_wIntorno/2+1+j] + greenvalues2[i][j])/2;
                       blue = (bluevalues1[i][_wIntorno/2+1+j] + bluevalues2[i][j])/2;*/
                       value = (values1[_wIntorno/2+1+i][j] + values2[i][j])/2;
                       _output_image.setRGB(centerX-_wIntorno/2+i, centerY+j, new Color(value, value, value).getRGB());
                   }
            }
            else if(distX == distY) //Siamo nel caso diagonale
            {
                for(int i=0; i<_wIntorno/2; i++)//Righe
                   for(int j=0; j<_wIntorno/2; j++)//Colonne
                   {
                       /*red = (redvalues1[_wIntorno/2+1+i][_wIntorno/2+1+j] + redvalues2[i][j])/2;
                       green = (greenvalues1[_wIntorno/2+1+i][_wIntorno/2+1+j] + greenvalues2[i][j])/2;
                       blue = (bluevalues1[_wIntorno/2+1+i][_wIntorno/2+1+j] + bluevalues2[i][j])/2;*/
                       value = (values1[_wIntorno/2+1+i][j] + values2[i][j])/2;
                       _output_image.setRGB(centerX+i, centerY+j, new Color(value, value, value).getRGB());
                   }
            }
        }
        
 
        
 
        
        public BufferedImage getOutputImage()
        {
            return _output_image;
        }
        
}
